package main

import (
	"bufio"
	"bytes"
	"crypto/rand"
	"encoding/binary"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"log"
	"math/big"
	"os"
	"strconv"
	"time"
)

var defaultAddr = "127.0.0.1:8000"

type client struct {
	//节点信息
	node *node
	//pbft节点总数
	nodeCount int
	//发送消息的记录池
	messagePool map[int64]string
	//业务记录池
	traPool map[int64]string
	//存储pbft节点的id和addr
	pbftNodeTable map[string]string
	//记录reply数量，判断是否收到足够数量的reply
	replyPool map[int]map[int64]map[string]bool
	//视图
	view int
	//存储查询结果
	queryTable map[int64]string
}

func NewClient(clientId int) *client{
	c := new(client)

	c.node = NewNode(clientId)
	c.messagePool = make(map[int64]string)
	c.traPool = make(map[int64]string)
	c.pbftNodeTable = make(map[string]string)
	c.replyPool = make(map[int]map[int64]map[string]bool)
	c.view = 0
	c.queryTable =make(map[int64]string)
	c.node.nodeJoin(defaultAddr)

	return c
}
var channel = make(chan int64)

func (c *client) handleReply(content []byte){
	//签名验证
	r := new(Reply)
	err := json.Unmarshal(content, r)
	if err != nil{
		log.Panic(err)
	}
	switch r.ResponseMessage.Type {
	case oNewNode:
		c.handleNewNodeReply(r)
	case oNewTra:
		c.handleNewTraReply(r)
	case oVerTra:
		c.handleVriTraReply(r)
	}
}

func (c *client) handleQuery(content []byte) {
	//log.Println("收到回复")
	q := new(Query)
	err := json.Unmarshal(content, q)
	if err != nil{
		log.Println(err)
	}
	//验签
	//验证是否发出
	c.queryTable[q.SequenceID] = q.QueryReply
	channel <- q.SequenceID
	if strconv.FormatInt(q.SequenceID, 10) == c.messagePool[q.Timestamp]{
		fmt.Printf("收到来自%d节点的查询回复：%s\n", q.NodeId, q.QueryReply)
	}
}

func (c *client) clientSendMessageAndListen() {
	//开启客户端的本地监听（主要用来接收节点的reply信息）
	go c.clientTcpListen()
	fmt.Printf("客户端开启监听，地址：%s\n", c.node.addr)

	fmt.Println(" ---------------------------------------------------------------------------------")
	fmt.Println("|  已进入PBFT测试Demo客户端，请启动全部节点后再发送消息！ :)  |")
	fmt.Println(" ---------------------------------------------------------------------------------")
	fmt.Println("请在下方输入要存入节点的信息：")
	//首先通过命令行获取用户输入
	stdReader := bufio.NewReader(os.Stdin)
	for {
		data, err := stdReader.ReadString('\n')
		if err != nil {
			fmt.Println("Error reading from stdin")
			panic(err)
		}
		if data[:5] == "query"{
			len := len(data)
			c.sendQuery(data[5:len-2])
		}else{
			r := new(Request)
			r.Timestamp = time.Now().UnixNano()
			r.ClientAddr = c.node.addr
			r.Operation = oNewTra
			r.Message.Digest = c.node.msgDigest(data)
			//消息内容就是用户的输入
			r.Message.Content = "message"
			digestByte,_ := hex.DecodeString(r.Message.Digest)
			r.Sign = c.node.RsaSignWithSha256(digestByte, c.node.rsaPrivKey)

			c.messagePool[r.Timestamp] = data
			br, err := json.Marshal(r)
			if err != nil {
				log.Panic(err)
			}
			//log.Println(string(br))
			content := jointMessage(cRequest, br)
			go c.simpleTimer(r.Timestamp,content)
			//默认N0为主节点，直接把请求信息发送至N0
			mainNode := strconv.Itoa(c.view % nodeCount)
			//log.Printf("正在向主节点%s，地址%s发送信息\n", mainNode, nodeTable[mainNode])
			tcpDial(content, nodeTable[mainNode])
		}
	}
}

func (c *client)sendQuery(data string){
	q := new(Query)
	q.Timestamp = time.Now().UnixNano()
	//todo
	//改成正则匹配
	q.SequenceID,_ = strconv.ParseInt(data,10,64)
	//缺少错误输出
	//if err != nil{
	//	log.Println(err)
	//}
	c.messagePool[q.Timestamp] = strconv.FormatInt(q.SequenceID,10)
	//log.Println(q.SequenceID)
	q.ClientAddr = c.node.addr
	q.QueryType = oNewTra
	br, err := json.Marshal(q)
	if err != nil {
		log.Panic(err)
	}
	//log.Println(string(br))
	content := jointMessage(cQuery, br)
	//默认查询N1
	node := strconv.Itoa(c.view+1 % nodeCount)
	//log.Printf("正在向主节点%s，地址%s发送信息\n", mainNode, nodeTable[mainNode])
	tcpDial(content, nodeTable[node])
}

func (c *client) handleNewNodeReply(r *Reply) {
	c.nodeCount = r.ResponseMessage.NodeCount
	c.pbftNodeTable[strconv.Itoa(r.NodeID)] = r.ResponseMessage.NodeAddr
	count := 0
	for range c.pbftNodeTable {
		count++
	}
	eventId := r.ResponseMessage.SequenceID
	if count > nodeCount / 3 * 2 && c.traPool[eventId] == "" {
		c.traPool[eventId] = "本节点已加入网络！"
		c.view = r.View
		s := "业务ID:" + strconv.FormatInt(eventId, 10) + "本客户端节点已经录入系统"
		fmt.Println(s)
		fmt.Println(c.traPool)
	}
}

func (c *client) handleNewTraReply(r *Reply) {
	c.setReplyConfirmMap(r.View, r.Timestamp, strconv.Itoa(r.NodeID), true)
	count := 0
	for range c.replyPool[r.View][r.Timestamp] {
		count++
	}
	eventId := r.ResponseMessage.SequenceID
	if count > nodeCount / 3 * 2 && c.traPool[eventId] == "" {
		//c.traPool[eventId] = c.messagePool[r.Timestamp]
		c.traPool[eventId] = "hello"
		//r.ResponseMessage.Response
		c.view = r.View
		s := "业务ID:" + strconv.FormatInt(eventId, 10) + "已达成全网共识!"
		fmt.Println(s)
		fmt.Println(c.traPool)
		//发送给客户端4号
		fmt.Println("即将发往client :5011")
		req := new(Request)

		//
		//req.Timestamp = time.Now().UnixNano()
		//req.ClientAddr = c.node.addr
		req.Operation = oTransaction
		req.Message.Digest = strconv.FormatInt(r.ResponseMessage.SequenceID,10)
		//消息内容就是用户的输入
		req.Message.Content = c.messagePool[r.Timestamp]
		br, err := json.Marshal(req)
		if err != nil {
			log.Panic(err)
		}
		content := jointMessage(cRequest, br)
		//go c.simpleTimer(r.Timestamp,content)
		tcpDial(content, c.node.addr)
	}
}

func (c *client) handleVriTraReply(r *Reply) {
	c.setReplyConfirmMap(r.View, r.Timestamp, strconv.Itoa(r.NodeID), true)
	count := 0
	for range c.replyPool[r.View][r.Timestamp] {
		count++
	}
	eventId := r.ResponseMessage.SequenceID
	if count > nodeCount / 3 * 2 && c.traPool[eventId] == "" {
		//c.traPool[eventId] = c.messagePool[r.Timestamp]
		fmt.Println(c.traPool)
		c.traPool[eventId] = "hello"
		//r.ResponseMessage.Response
		c.view = r.View
		s := "业务ID:" + strconv.FormatInt(eventId, 10) + "已达成全网共识"
		fmt.Println(s)
		fmt.Println(c.traPool)
	}
}

func (c *client) handleRequest(content []byte) {
	//todo解码

	r := new(Request)
	err := json.Unmarshal(content, r)
	if err != nil {
		log.Panic(err)
	}
	if r.Operation != oTransaction{

	}else{
		//1、查询hash
		go c.sendQuery(r.Message.Digest)
		//2、比对hash
		seqId, err := strconv.ParseInt(r.Message.Digest, 10, 64)
		if err != nil{
			log.Println(err)
		}

		i := <-channel
		searchHash := c.queryTable[seqId]
		if searchHash == ""{

		}else if strconv.FormatInt(i,10) != r.Message.Digest{
		}else if searchHash == c.node.msgDigest(r.Message.Content){
			//3、hash相同
			//3-1、checked
			r.Content = string(jointTail([]byte(r.Digest)))
			r.Digest = c.node.msgDigest(searchHash)

			//3-2、共识
			r.Timestamp = time.Now().UnixNano()
			r.ClientAddr = c.node.addr
			r.Operation = oVerTra

			digestByte,_ := hex.DecodeString(r.Digest)
			r.Sign = c.node.RsaSignWithSha256(digestByte, c.node.rsaPrivKey)
			br, err := json.Marshal(r)
			if err != nil {
				log.Panic(err)
			}
			content := jointMessage(cRequest, br)
			go c.simpleTimer(r.Timestamp,content)
			mainNode := strconv.Itoa(c.view % c.nodeCount)
			//log.Println(r)
			tcpDial(content, c.pbftNodeTable[mainNode])
		}else{
			log.Println("hash wrong")
			log.Println(r.Message.Digest)
			log.Println(searchHash)
			//4、hash不同
			//4-1、获取ip-route
			//4-2warning
		}
	}
}

//返回一个十位数的随机数，作为msgid
func (c *client)getRandom() int {
	x := big.NewInt(10000000000)
	for {
		result, err := rand.Int(rand.Reader, x)
		if err != nil {
			log.Panic(err)
		}
		if result.Int64() > 1000000000 {
			return int(result.Int64())
		}
	}
}

//多重赋值
func (c *client)setReplyConfirmMap(view int, val int64, val2 string, b bool) {
	if _, ok := c.replyPool[view]; !ok{
		c.replyPool[view] = make(map[int64]map[string]bool)
	}
	if _, ok := c.replyPool[view][val]; !ok {
		c.replyPool[view][val] = make(map[string]bool)
	}
	c.replyPool[view][val][val2] = b
}

func (c *client)simpleTimer(ts int64,r []byte){
	time.Sleep(time.Second * 2)
	count := 0
	for range c.replyPool[c.view][ts] {
		count++
	}
	if count > nodeCount / 3 * 2{
		delete(c.replyPool[c.view], ts)
	}else{
		fmt.Println("客户端超时重发：Re-request")
		c.broadcast(r)
	}
}

//向除自己外的其他节点进行广播
func (c *client)broadcast(r []byte) {
	for i := range nodeTable {
		go tcpDial(r, nodeTable[i])
	}
}

func (c *client)BytesToInt(bys []byte) int64 {
	byteBuff := bytes.NewBuffer(bys)
	var data int64
	binary.Read(byteBuff, binary.BigEndian, &data)
	return data
}
